#include "pch.hpp"
#include "MainWindow.hpp"
#include "AboutDialog.hpp"
#include <QtUi/Application.hpp>




MainWindow::MainWindow(QWidget *parent)
	: QMainWindow(parent)
	, m_settings("Taleon-Engine", "UiEdit", this)

	, m_scheduler(QtUi::Application::instance()->serviceProvider()->service<services::scheduler>())

	, m_changedSinceParsing(false)

	, m_editWidget(NULL)
{
	///////////////////
	// Initialize Ui //
	///////////////////

	ui.setupUi(this);

	// Connect signals
	connect(ui.actionNew,      SIGNAL(triggered()), SLOT(onNew()));
	connect(ui.actionOpen,     SIGNAL(triggered()), SLOT(onOpen()));
	connect(ui.actionSave,     SIGNAL(triggered()), SLOT(onSave()));
	connect(ui.actionSave_as,  SIGNAL(triggered()), SLOT(onSaveAs()));
	connect(ui.actionExit,     SIGNAL(triggered()), SLOT(onExit()));
	connect(ui.actionAddGlyph, SIGNAL(triggered()), SLOT(onAddGlyph()));
	connect(ui.actionAddFont,  SIGNAL(triggered()), SLOT(onAddFont()));
	connect(ui.actionRemove,   SIGNAL(triggered()), SLOT(onRemove()));
	connect(ui.actionAbout,    SIGNAL(triggered()), SLOT(onAbout()));
	connect(ui.actionAboutQt,  SIGNAL(triggered()), SLOT(onAboutQt()));

	connect(ui.glyphView,      SIGNAL(clicked(QModelIndex)), SLOT(onGlyphClicked(QModelIndex)));
	connect(ui.fontView,       SIGNAL(clicked(QModelIndex)), SLOT(onFontClicked(QModelIndex)));
	connect(ui.buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(onEditApply()));
	connect(ui.buttonBox->button(QDialogButtonBox::Discard), SIGNAL(clicked()), SLOT(onEditDiscard()));

	connect(&m_timer, SIGNAL(timeout()), SLOT(onUpdate()));
	m_timer.start(500);


	ui.glyphFontDock->toggleViewAction()->setIcon(QIcon(":/icons/font_go"));
	ui.menuView->addAction(ui.glyphFontDock->toggleViewAction());

	ui.previewDock->toggleViewAction()->setIcon(QIcon(":/icons/eye"));
	ui.menuView->addAction(ui.previewDock->toggleViewAction());

	ui.styleDock->toggleViewAction()->setIcon(QIcon(":/icons/style_edit"));
	ui.menuView->addAction(ui.styleDock->toggleViewAction());

	m_syntaxHighlighter.setErrorModel(&m_errorModel);
	ui.errorView->setModel(&m_errorModel);
	QHeaderView* header = ui.errorView->horizontalHeader();
	header->setResizeMode(QHeaderView::ResizeToContents);

	restoreSettings();

	ui.cssEditor->setTabStopWidth(20);
	ui.cssEditor->setAcceptRichText(false);
	ui.cssEditor->setUndoRedoEnabled(true);


	//////////////
	// Document //
	//////////////

	onNew();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

MainWindow::~MainWindow()
{
	ui.cssEditor->setDocument(NULL);
	m_syntaxHighlighter.setDocument(NULL);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void MainWindow::setDocument(const Document::ptr& document)
{
	setEditWidget(NULL);
	ui.cssEditor->setDocument(NULL);
	m_syntaxHighlighter.setDocument(NULL);


	// Well, store the new document
	m_document = document;
	m_cssContent = document->cssDocument()->toPlainText();

	// Window title
	QString fileName;
	if(document->fileName() == "") fileName = "Unnamed";
	else                           fileName = QFileInfo(document->fileName()).completeBaseName();


	/////////
	// GUI //
	/////////

	m_glyphModel.setDocument(document);
	m_fontModel.setDocument(document);

	m_errorModel.clear();

	ui.glyphView->setModel(&m_glyphModel);
	ui.fontView->setModel(&m_fontModel);

	ui.cssEditor->setDocument(document->cssDocument());
	m_syntaxHighlighter.setDocument(ui.cssEditor->document());


	//
	// Signals
	//

	connect(document->cssDocument(), SIGNAL(contentsChanged()), SLOT(onCssDocumentChanged()));
	connect(ui.glyphView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(onSelectionChanged()));
	connect(ui.fontView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(onSelectionChanged()));

	setWindowTitle(fileName);
	onEditChanged(); //< Reset the button box for the edit widget
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::setWindowTitle(QString fileName)
{
	QMainWindow::setWindowTitle(QString("%1 - UiEdit [*]").arg(fileName));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::setDocumentChanged(bool changed)
{
	m_changedSinceParsing = true;
	setWindowModified(changed);
	ui.actionSave->setEnabled(changed);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::setEditWidget(EditWidget* widget)
{
	if(m_editWidget)
	{
		// We test if the given widget belongs to the same item in the same view
		// as the currently displayed edit widget, because when it does, nothing is done...
		if(widget && widget->belongsToView(m_editWidget->itemView(), m_editWidget->index()))
		{
			delete widget;
			return;
		}

		if(m_editWidget->hasChanged())
		{
			QMessageBox msgBox(this);
			msgBox.setText(tr("The glyph/font has been modified."));
			msgBox.setInformativeText(tr("Do you want to save your changes?"));
			msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
			msgBox.setDefaultButton(QMessageBox::Save);
			int ret = msgBox.exec();

			switch (ret)
			{
			case QMessageBox::Save:
				// Save was clicked: Save the data of the current widget
				m_editWidget->apply();
				break;
			case QMessageBox::Discard:
				// Don't Save was clicked: Trash the data
				break;
			case QMessageBox::Cancel:
				// Cancel was clicked: Don't display the new widget
				delete widget;
				return;
			}
		}

		delete m_editWidget;
		m_editWidget = NULL;
	}

	if(widget)
	{
		m_editWidget = widget;
		ui.editWidget_placeholder->setWidget(m_editWidget);
		connect(m_editWidget, SIGNAL(changed()), SLOT(onEditChanged()));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void MainWindow::onNew()
{
	// This function doesn't catch because it isn't supposed
	// to throw in the first place
	if(shouldClose())
	{
		setDocument(boost::make_shared<Document>());
		setDocumentChanged(false);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onOpen()
{
	if(shouldClose())
	{
		QString fileName = QFileDialog::getOpenFileName(this, tr("Select style document"), "", tr("Style documents (*.xml)"));
		if(!fileName.isEmpty())
		{
			try
			{
				setDocument(boost::make_shared<Document>(fileName));
				setDocumentChanged(false);

				// Let's see if we can apply that style right-away
				m_changedSinceParsing = true;
				onUpdate();
			}
			catch(const exception& except)
			{
				QMessageBox msgBox(this);
				msgBox.setWindowTitle(tr("Unable to load style definition"));
				msgBox.setText(QString(tr("The given style definition cannot be loaded: %1")).arg(except.what()));
				msgBox.setDetailedText(QString(tr("Type: boost_ext::exception\nMessage: %1\nFile: %2\nFunction: %3\nLine: %4")).arg(except.what()).arg(except.file()).arg(except.function()).arg(except.line()));

				msgBox.setIconPixmap(QPixmap(":/ico/warning"));
				msgBox.addButton(QMessageBox::Ok);

				msgBox.exec();
			}
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onSave()
{
	if(m_document->fileName().isEmpty())
	{
		QString fileName = QFileDialog::getSaveFileName(this, tr("Select style document"), "", tr("Style documents (*.xml)"));
		if(!fileName.isEmpty())
		{
			m_document->save(fileName);
			setDocumentChanged(false);
		}
	}
	else
	{
		m_document->save();
		setDocumentChanged(false);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onSaveAs()
{
	QString fileName = QFileDialog::getSaveFileName(this, tr("Select style document"), "", tr("Style documents (*.xml)"));
	if(!fileName.isEmpty())
	{
		m_document->save(fileName);
		setDocumentChanged(false);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onExit()
{
	if(shouldClose())
	{
		writeSettings();
		exit(0);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onAddGlyph()
{
	QStringList fileNames = QFileDialog::getOpenFileNames(QApplication::activeWindow(), "Select image(s)", "", tr("Images (*.png *.xpm *.jpg *.jpeg)"));

	try
	{
		foreach(QString fileName, fileNames)
		{
			m_glyphModel.add(AdvancedGlyph(fileName));
		}

		if(fileNames.size())
		{
			setDocumentChanged(true);
		}
	}
	catch(const exception& except)
	{
		QMessageBox msgBox(this);
		msgBox.setWindowTitle(tr("Unable to add glyph"));
		msgBox.setText(QString(tr("The given glyph cannot be added to the style defintion: %1")).arg(except.what()));
		msgBox.setDetailedText(QString(tr("Type: boost_ext::exception\nMessage: %1\nFile: %2\nFunction: %3\nLine: %4")).arg(except.what()).arg(except.file()).arg(except.function()).arg(except.line()));

		msgBox.setIconPixmap(QPixmap(":/ico/warning"));
		msgBox.addButton(QMessageBox::Ok);

		msgBox.exec();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onAddFont()
{
	QStringList fileNames = QFileDialog::getOpenFileNames(QApplication::activeWindow(), "Select font(s)", "", tr("Bitmap fonts (*.fnt)"));

	try
	{
		foreach(QString fileName, fileNames)
		{
			m_fontModel.add(AdvancedFont(fileName));
		}

		if(fileNames.size())
		{
			setDocumentChanged(true);
		}
	}
	catch(const exception& except)
	{
		QMessageBox msgBox(this);
		msgBox.setWindowTitle(tr("Unable to add glyph"));
		msgBox.setText(QString(tr("The given font cannot be added to the style defintion: %1")).arg(except.what()));
		msgBox.setDetailedText(QString(tr("Type: boost_ext::exception\nMessage: %1\nFile: %2\nFunction: %3\nLine: %4")).arg(except.what()).arg(except.file()).arg(except.function()).arg(except.line()));

		msgBox.setIconPixmap(QPixmap(":/ico/warning"));
		msgBox.addButton(QMessageBox::Ok);

		msgBox.exec();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onCssDocumentChanged()
{
	if(!m_document)
		return;

	QString content = m_document->cssDocument()->toPlainText();
	if(content != m_cssContent)
	{
		m_cssContent = content;
		setDocumentChanged(true);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onRemove()
{
	// Close the edit widget, if the removed item is that one
	QItemSelectionModel* model;

	// Delete all selected glyphs
	model = ui.glyphView->selectionModel();
	if(ui.glyphView->hasFocus() && model->hasSelection())
	{
		QModelIndexList indices = model->selectedIndexes();
		foreach(const QModelIndex& index, indices)
		{
			if(m_editWidget && m_editWidget->belongsToView(ui.glyphView, index))
			{
				setEditWidget(NULL);
			}

			m_glyphModel.remove(index);
		}
	}

	// Delete all selected fonts
	model = ui.fontView->selectionModel();
	if(ui.fontView->hasFocus() && model->hasSelection())
	{
		QModelIndexList indices = model->selectedIndexes();
		foreach(const QModelIndex& index, indices)
		{
			if(m_editWidget && m_editWidget->belongsToView(ui.fontView, index))
			{
				setEditWidget(NULL);
			}

			m_fontModel.remove(index);
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void MainWindow::onGlyphClicked(const QModelIndex& index)
{
	AdvancedGlyph& glyph = m_glyphModel.glyph(index);
	setEditWidget(new GlyphWidget(glyph, ui.glyphView, index));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onFontClicked(const QModelIndex& index)
{
	AdvancedFont& font = m_fontModel.font(index);
	GlyphWidget* widget = new GlyphWidget(font, ui.fontView, index);
	widget->setReadOnly(true);
	setEditWidget(widget);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onEditChanged()
{
	// The signal is emitted whenever the user changes something in the dialog, but
	// this could deliberately undo a previous change, that's why we check if the
	// data in the dialog matches the underlying data or not through changed()
	if(m_editWidget && m_editWidget->hasChanged())
	{
		QPushButton* apply = ui.buttonBox->button(QDialogButtonBox::Apply);
		apply->setEnabled(true);

		QPushButton* discard = ui.buttonBox->button(QDialogButtonBox::Discard);
		discard->setEnabled(true);
	}
	else
	{
		QPushButton* apply = ui.buttonBox->button(QDialogButtonBox::Apply);
		apply->setDisabled(true);

		QPushButton* discard = ui.buttonBox->button(QDialogButtonBox::Discard);
		discard->setDisabled(true);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onEditApply()
{
	m_editWidget->apply();
	setDocumentChanged(true);
	onEditChanged();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onEditDiscard()
{
	m_editWidget->discard();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onSelectionChanged()
{
	ui.actionRemove->setEnabled(
		ui.glyphView->selectionModel()->hasSelection() || ui.fontView->selectionModel()->hasSelection()
		);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onAbout()
{
	AboutDialog dlg(this);
	dlg.exec();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onAboutQt()
{
	QMessageBox::aboutQt(this);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::onUpdate()
{
	if(!m_document || !m_changedSinceParsing)
		return;

	QString text = m_document->cssDocument()->toPlainText();
	if(!text.size())
		return;

	m_parserThread.queueStyle(m_document->styleDocument(), text);

	boost::optional<ParserThread::OutputItem> item = m_parserThread.popStyle();
	if(item)
	{
		m_errorModel.clear();
		

		if(item->style)
		{
			EnginePtr engine = ui.graphicsView->engine();
			UiSystemPtr system = engine->uiSystem();
			TexturePtr texture = engine->videoDevice()->createTexture(item->atlas);

			// If parsing was successful, display the new style
			system->setStyle(item->style, texture);

			ui.errorsButton->setText(tr("0 Errors"));

			m_changedSinceParsing = false;
		}
		else
		{
			// If parsing was unsuccessfull, display the errors
			m_errorModel.add(item->errors);

			ui.errorsButton->setText(QString(tr("%1 Errors")).arg(m_errorModel.rowCount()));
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void MainWindow::closeEvent(QCloseEvent* event)
{
	if(shouldClose())
	{
		writeSettings();
		event->accept();
	}
	else
	{
		event->ignore();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool MainWindow::shouldClose()
{
	if(isWindowModified() && m_document)
	{
		QString fileName = m_document->fileName();
		if(fileName.isEmpty())
			fileName = "Unnamed";

		QMessageBox msgBox(this);
		msgBox.setText(QString(tr("The document '%1' has been modified.")).arg(fileName));
		msgBox.setInformativeText(tr("Do you want to save your changes?"));
		msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
		msgBox.setDefaultButton(QMessageBox::Save);
		int ret = msgBox.exec();

		switch (ret)
		{
		case QMessageBox::Save:
			// Save was clicked: Save the data of the current widget
			m_document->save();
			return true;
		case QMessageBox::Discard:
			// Don't Save was clicked: Close the application without saving first
			return true;
		case QMessageBox::Cancel:
			// Cancel was clicked: Don't close the application
			return false;
		default:
			BOOSTEXT_THROW(programming_error("Unintended path of execution"));
		}
	}
	else
	{
		return true;
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::writeSettings()
{
	m_settings.beginGroup("mainwindow");
	m_settings.setValue("state", saveState());
	m_settings.setValue("pos", pos());
	m_settings.setValue("size", size());

	// TODO: save state of more items that are childs of the mainwindow
	m_settings.endGroup();

	// TODO: save general state of items, unrelated to the mainwindow
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::restoreSettings()
{
	m_settings.beginGroup("mainwindow");
	if(m_settings.contains("state")) restoreState(m_settings.value("state").toByteArray());
	if(m_settings.contains("pos")) move(m_settings.value("pos").toPoint());
	if(m_settings.contains("size")) resize(m_settings.value("size").toSize());

	// TODO: restore state of child items of the mainwindow
	m_settings.endGroup();

	// TODO: restore state of items unrelated to the mainwindow
}
///////////////////////////////////////////////////////////////////////////////////////////////////
